'use strict';

import url from 'url';

export default class extends think.adapter.base {
  /**
   * init
   * @param  {Object} server []
   * @param  {Object} config []
   * @return {Object}        []
   */
  init(server, config, app){
    this.server = server;
    this.config = config;
    this.app = app;
  }
  /**
   * check origin allowed
   * @param  {String}  origin []
   * @return {Boolean}        [] 
   */
  isOriginAllowed(origin){
    let allowOrigins = this.config.allow_origin;
    if (!allowOrigins) {
      return true;
    }
    let info = url.parse(origin);
    let hostname = info.hostname;
    if (think.isString(allowOrigins)) {
      return allowOrigins === hostname;
    }else if (think.isArray(allowOrigins)) {
      return allowOrigins.indexOf(hostname) > -1;
    }else if (think.isFunction(allowOrigins)) {
      return allowOrigins(hostname, info);
    }
    return false;
  }
  /**
   * run
   * @return {} []
   */
  async run(){
    let socketio = await think.npm('socket.io');

    let engine   = await think.npm('engine.io');

    let amqp     = await think.npm('amqp');

    // this.server.pauseOnConnect = true;

    // this.server.on("connection", (connection)=>{
    //   connection.pause();
    //   connection.resume();
    // });

    let io = socketio(this.server, this.config.options);

    // let errors = {
    //   UNKNOWN_TRANSPORT: 0,
    //   UNKNOWN_SID: 1,
    //   BAD_HANDSHAKE_METHOD: 2,
    //   BAD_REQUEST: 3,
    //   FORBIDDEN: 4
    // };

    // io.eio.verify = function (req, upgrade, fn) {
    //   // transport check
    //   // console.log(req._query.transport);
    //   var transport = req._query.transport;
    //   if (!~io.eio.transports.indexOf(transport)) {
    //     think.log('unknown transport "%s"', transport);
    //     return fn(errors.UNKNOWN_TRANSPORT, false);
    //   }

    //   // sid check
    //   var sid = req._query.sid;
    //   if (sid) {
    //     var transportName = transport;
    //     if (!io.eio.clients.hasOwnProperty(sid) && 'polling' == transportName) {
    //       try {
    //         var transport = new engine.transports[transport](req);
    //         if ('polling' == transportName) {
    //           transport.maxHttpBufferSize = this.maxHttpBufferSize;
    //         }
    //         if (req._query && req._query.b64) {
    //           transport.supportsBinary = false;
    //         } else {
    //           transport.supportsBinary = true;
    //         }
    //       }
    //       catch (e) {
    //         return fn(errors.BAD_REQUEST, false);
    //       }
    //       console.log(666666);
    //       var socket = new engine.Socket(sid, io.eio, transport, req);

    //       io.eio.clients[sid] = socket;
    //       io.eio.clientsCount++;

    //       socket.once('close', function(){
    //         console.log("close: "+ process.pid);
    //         delete io.eio.clients[sid];
    //         io.eio.clientsCount--;
    //       });

    //       io.eio.emit('connection', socket);

    //       return transport.onRequest(req);
    //     }

    //     if (!io.eio.clients.hasOwnProperty(sid)){
    //       return fn(errors.UNKNOWN_SID, false);
    //     }

    //     if (!upgrade && io.eio.clients[sid].transport.name !== transportName) {
    //       think.log('bad request: unexpected transport without upgrade');
    //       return fn(errors.BAD_REQUEST, false);
    //     }
    //   } else {
    //     // handshake is GET only
    //     if ('GET' !== req.method) return fn(errors.BAD_HANDSHAKE_METHOD, false);
    //     if (!io.eio.allowRequest) return fn(null, true);
    //     return io.eio.allowRequest(req, fn);
    //   }

    //   fn(null, true);
    // };

    this.io        = io;

    let Storage    = think.service("storage", "socket"); //加载 socket 模块下的 storage service

    let Room       = think.service("room", "socket"); //加载 socket 模块下的 room service

    this.Rooms     = new Room();

    this.Broadcast = new Storage(); 

    this.Msg       = think.service("msg", "socket");

    if(this.config.adp){
      io.adapter(this.config.adp());
    }

    //Sets the path v under which engine.io and the static files will be served. Defaults to /socket.io.
    if(this.config.path){
      io.path(this.config.path);
    }

    //Sets the allowed origins v. Defaults to any origins being allowed.
    let allow_origin = this.config.allow_origin;
    if(allow_origin){
      io.origins(this.config.allow_origin);
    }

    //get message type
    let messages = think.isArray(this.config.messages) ? this.config.messages : [this.config.messages];
    messages.forEach((v = {}) => {
      let sc = v.namespace ? io.of(v.namespace) : io;
      this.registerSocket(sc, v);
    });

    try {
      // statements
      this.registerAmqp(amqp);
    } catch(e) {
      // statements
      think.log(e);
    }
  }

  registerAmqp(mq){
    let Amqp    = think.service("amqp", "socket"); 

    this.amqp = new Amqp({
      uuid      : think.uuid(16),
      serverName: think.config("serverName"),
      amqp      : think.config("amqp"),
      mq        : mq
    });

    this.amqp.on("receive", (msg)=>{
        let m       = new this.Msg(Object.assign(msg, {
            subscribe: msg.data.data.room,
            data     : msg.data.data,
            type     : msg.data.type
        }), true);
  
        if('type' in m && m.type === 10 && !think.config("realTimeOnline")){
          this.Rooms.setOnline(m.subscribe, m.data.online);
        }
        switch (m.type) {
          case 10:
            if(!think.config("realTimeOnline")){
              break;
            }
          default:
            if(m.type < 100){
              try {
                  if( 0 == m.subscribe ){
                    this.broadcastAllRoom(m, true);
                  }else{
                    this.broadcastRoom(m, m.subscribe, true);
                  }
              } catch (error) {
                  think.log(error);
              }
            }
            break;
        }
    });
  }

  /**
   * register namespace of socket, and support multi socket connect
   * eg:
   * export default {
    messages:
        [
            {
                namespace:'/payCount',
                open: 'analysis/erp_pay/open',
                close: 'analysis/erp_pay/close',
                day: 'analysis/erp_pay/day',
                updateFromMq: 'analysis/erp_pay/updateFromMq',
            }
        ]
    };
   * @param io
   * @param messages
     */
  registerSocket(io, messages){
    let msgKeys = Object.keys(messages);
    let open = messages.open;
    delete messages.open;
    let close = messages.close;
    delete messages.close;

    thinkCache(thinkCache.WEBSOCKET, io.sockets.sockets);

    io.on('connection', socket => {
      //open connection
      if(open){
        this._message(open, undefined, socket);
      }
      //listen disonnection event
      if(close){
        socket.on('disconnect', () => {
          this._message(close, undefined, socket);
        });
      }

      //listen list of message type
      msgKeys.forEach(msgKey => {
        socket.on(msgKey, msg => {
          this._message(messages[msgKey], msg, socket);
        });
      });
    });
  }
  /**
   * emit socket data
   * @param  {String} event []
   * @param  {Mixed} data  []
   * @return {}       []
   */
  emit(event, data){
    return this.socket.emit(event, data);
  }
  /**
   * broadcast socket data
   * @param  {String} event       []
   * @param  {Mixed} data        []
   * @param  {Boolean} containSelf []
   * @return {}             []
   */
  broadcast(event, data, containSelf){
    if(containSelf){
      this.io.sockets.emit(event, data);
    }else{
      this.socket.broadcast.emit(event, data);
    }
  }
  /**
   * deal message
   * @param  {String} url  []
   * @param  {Mixed} data []
   * @return {}      []
   */
  async _message(url, data, socket){
    let request = socket.request;
    if(url[0] !== '/'){
      url = `/${url}`;
    }
    request.url = url;
    let http;
    //socket.io c++ client发过来的requet没有res
    if(!request.res){
        http = await think.http(url);
    }else{
        http = await think.http(request, think.extend({}, request.res));
    }
    http.data       = this._decoder(data);
    
    http.socket     = socket;

    http.id         = socket.id;
    
    http.io         = this.io;

    http.Rooms      = this.Rooms;

    http.Broadcast  = this.Broadcast;

    http.Msg        = this.Msg;

    http.socketEmit = this.emit;

    http.socketBroadcast = this.broadcast;

    http.broadcastRoom = this.broadcastRoom;

    http.joinRoom   = this.joinRoom;

    http.leaveRoom  = this.leaveRoom;

    http.updateUser = this.updateUser;

    http.getUser    = this.getUser;

    http.errMsg     = this.errMsg;

    http.report     = this.report;

    http.online     = this.online;

    http.qos        = this.qos;

    http.message    = this.message;

    http.publish    = this.publish;

    http.online     = this.online;

    http._emit      = this._emit;

    http.emit       = this.emit;

    http.reply      = this.reply;

    http.amqp       = this.amqp;

    http.broadcastRoom = this.broadcastRoom;

    http.broadcastAllRoom = this.broadcastAllRoom;

    // http.getUser  = (...args)=>{
    //   this.getUser.apply(this, [socket].concat(args));
    // };
    let instance = new this.app(http);
    return instance.run();
  }

  _decodeUrl(socket){
    for(let k in socket.handshake.query){
      socket.handshake.query[k] = decodeURI(socket.handshake.query[k]);
    }
    return socket;
  }

  broadcastRoom(msg, room, extend = false){
      msg = msg || this.data;

      let rooms = room ? [room] : this.Rooms.rooms(this.socket);

      rooms.forEach((roomid)=>{
        if(this.Rooms.has(roomid)){
          for (let sid of this.Rooms.get(roomid).keys()) {
            this.message(msg, extend, this.io.sockets.sockets[sid] || this.io.sockets.connected[sid], sid);
          }
        }
      }); 
  }

  broadcastAllRoom(msg, extend = false){
      msg = msg || this.data;
      this.Rooms.forEach((sks, roomid)=>{
        msg.subscribe = roomid;
        for (let sid of this.Rooms.get(roomid).keys()) {
          this.message(msg, extend, this.io.sockets.sockets[sid] || this.io.sockets.connected[sid], sid);
        }
      });
  }

  /**
   * 发送message
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
   _emit(event, msg, socket, id){

      msg.event = event;
            
      (function(socket, event, msg, id, broadcast){

          if(!socket){
            return ;
          }
          socket.emit(event, msg);

          if(msg.qos && 'server' === msg.from){
        
              broadcast.ifNew(id).set(msg.id, msg);

              let timer = setInterval(function(){
                  if(!socket || !broadcast.has(id) || !broadcast.get(id).has(msg.id)){
                      clearInterval(timer);
                      if(broadcast.has(id)){
                          broadcast.get(id).delete(msg.id);
                      }
                      if(broadcast.has(id) && broadcast.get(id).size < 1){
                          broadcast.delete(id)
                      }
                  }else{
                      msg = broadcast.get(id).get(msg.id)
                      event = msg.event
                      msg.send++;
                      socket.emit(event, msg);
                      if(msg.send === 5){
                          broadcast.get(id).delete(msg.id);
                          if(broadcast.has(id) && broadcast.get(id).size < 1){
                              broadcast.delete(id)
                          }
                      }else{
                          if(broadcast.has(id)){
                              broadcast.get(id).set(msg.id, msg)
                          }
                      }
                      
                  }
              }, 5000)
          }

      })(socket || this.socket, event, msg, id || this.socket.id, this.Broadcast)
   }
  /**
   * 发送message
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
   message(msg, extend = false, socket, id){
      this._emit("message", new this.Msg(msg, extend).getObject(), socket, id);
   }

   /**
   * 发送message
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
   publish(msg, extend = false){
      try {
        this.amqp.publish(new this.Msg(msg, extend).getObject());
      } catch(e) {
        think.log(e);
      }
   }
   /**
   * 发送message
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
   reply(msg){

      msg      = msg || this.data;

      let m    = new this.Msg(msg, true);
    
      if('server' === m.from){
          if(!this.Broadcast.isExist(this.socket, msg)){
              return ;
          }
          m = this.Broadcast.get(this.socket.id).get(msg.id)
          switch (m.status) {
              case 's1':
                   m.status = 's2';
                  break;
              case 's2':
                   m.status = 'done';
                  break;
              default:
                   m.status = 'done';
                  break;
          }
          if(m.status === 'done' || m.qos === 1){
              if(this.Broadcast.has(this.socket.id)){
                  this.Broadcast.get(this.socket.id).delete(msg.id)
              }
          }else{
              m.send = 0;
              m.event= 'reply';
              this.Broadcast.get(this.socket.id).set(msg.id, m);
          }
      }else{
          this.emit("reply", msg);
      }
   }
  /**
   * 发送qos
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
   qos(msg, callback){

      switch (msg.qos) {
          case 1:
              callback ? callback(msg) : viod(0);
              this.reply(msg);
              break;
          case 2:
              if(!this.Broadcast.isExist(this.socket, msg)){
                  callback ? callback(msg) : viod(0);
              }
              this.reply(msg);
              break;
          default:
              callback ? callback(msg) : viod(0);
              break;
      }
   }
  /**
   * 发送房间消息
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
  report(msg, type = 10){
    msg = msg || {};
    msg.online = this.online(msg.room);
    try {
      this.amqp.report(msg, type);
    } catch(e) {
      think.log(e);
    }
  }
  /**
   * 发送错误消息
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
  errMsg(msg, errorno = 1000){
    if(this.data && this.data.subscribe){
      this.emit("message", {subscribe: this.data.subscribe, error: errorno, data: {type: errorno, content: msg}});
    }
    this.end();
  }
  /**
   * 加入房间
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
  joinRoom(room){
    return this.Rooms.join(room, this.socket);
  }

  /**
   * 离开房间
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
  leaveRoom(){
    try {
      delete this.socket.user;
    } catch(e) {
    }
    return this.Rooms.leave(this.socket);
  }

  /**
   * 离开房间
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
  online(room, istrue = false){
    return this.Rooms.online(room, istrue);
  }

  getOnline(room){
    return this.Rooms.getOnline(room);
  }

  setOnline(room, value){
    return this.Rooms.setOnline(room, value);
  }

  /**
   * 更新用户信息
   * @param  {Object} socket  {}
   * @param  {String} room string
   * @return null
   */
  updateUser(user){
    this.socket.user = user;

    if(this.io.sockets.sockets[this.socket.id]){
      this.io.sockets.sockets[this.socket.id].user = user;
    }
    if(this.io.sockets.connected[this.socket.id]){
      this.io.sockets.connected[this.socket.id].user = user;
    }
  }

  /**
   * 获取用户信息
   * @param  {Object} socket  {}
   * @return {}
   */
  getUser(socket){
    if(this.io.sockets.sockets[this.socket.id]){
      return this.io.sockets.sockets[this.socket.id].user;
    }
    if(this.io.sockets.connected[this.socket.id]){
      return this.io.sockets.connected[this.socket.id].user;
    }
    return this.socket.user;
  }


  _encoder(msg){
    if(typeof msg === 'object'){
        try {
            return JSON.stringify(msg);
        } catch(e) {
            think.log(e);
            return {};
        }
    }else{
      return msg;
    }
  }

  _decoder(msg){
    if(!(typeof msg === 'object') && msg){
        try {
            msg = JSON.parse(msg);
        } catch(e) {
            think.log(msg);
            think.log(e);
            msg = {};
        }
    }
    return msg;
  }
}